/*
 * memory/fault.c
 *
 * Copyright (C) 2019 Aleksandar Andrejevic <theflash@sdf.lonestar.org>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <memory.h>

#define PAGE_ERROR_PROTECTION (1 << 0)
#define PAGE_ERROR_WRITING    (1 << 1)
#define PAGE_ERROR_USERMODE   (1 << 2)

static inline bool_t memory_resolve_unmapped_section(void *address, registers_t *regs, memory_block_t *block, memory_flags_t flags)
{
    page_num_t offset = block->section_offset + PAGE_NUMBER((uintptr_t)address) - block->address;
    UNUSED_PARAMETER(offset);

    // TODO: Not implemented
    return FALSE;
}

static inline bool_t memory_resolve_evicted(void *address, registers_t *regs, memory_flags_t flags)
{
    // TODO: Not implemented
    return FALSE;
}

static inline bool_t memory_resolve_not_accessible(void *address, registers_t *regs, memory_block_t *block, memory_flags_t flags)
{
    if (memory_get_page_mapping(memory_default_table, address)) return FALSE;

    if (flags & MEMORY_FLAG_EVICTED) return memory_resolve_evicted(address, regs, flags);
    if (block->section) return memory_resolve_unmapped_section(address, regs, block, flags);

    // TODO: Should go through the (future) page eviction subroutines instead
    page_t *page = memory_acquire_page(MIN_PHYS_ADDR_BITS, MAX_PHYS_ADDR_BITS, PAGE_SIZE);
    if (!page) KERNEL_CRASH_WITH_REGS("Out of free pages", regs);

    sysret_t ret = memory_map_page(memory_default_table, page, (void*)PAGE_ALIGN((uintptr_t)address), flags ? flags : block->flags);
    if (ret != ERR_SUCCESS) KERNEL_CRASH_WITH_REGS("Unexpected mapping failure", regs);

    return TRUE;
}

static inline bool_t memory_resolve_not_writable(void *address, registers_t *regs, memory_block_t *block, memory_flags_t flags)
{
    page_t *page = memory_get_page_mapping(memory_default_table, address);
    if (!page || !(flags & MEMORY_FLAG_COPY_ON_WRITE)) return FALSE;

    // TODO: Not implemented
    return FALSE;
}

bool_t memory_fault_handler(void *address, registers_t *regs)
{
    memory_block_t *block = memory_get_block_for_address(address);
    ASSERT(block != NULL);
    if (block->flags & MEMORY_FLAG_FREE) return FALSE;

    memory_flags_t flags = 0;
    memory_query_page_flags(memory_default_table, address, &flags);

    if (!(regs->error_code & PAGE_ERROR_PROTECTION)) return memory_resolve_not_accessible(address, regs, block, flags);
    else if (regs->error_code & PAGE_ERROR_WRITING) return memory_resolve_not_writable(address, regs, block, flags);
    else return FALSE;
}
